#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    int *p = (int *)malloc(sizeof(int));
    if (p == NULL)
    {
        // 内存分配失败
        return 1;
    }
    *p = 12;
    printf("%d\n", *p);
    free(p);
    p = NULL;

    //
    int *p2 = (int *)malloc(sizeof(int) * 10);
    if (p2 == NULL)
    {
        return 1;
    }
    for (int i = 0; i < 10; i++)
    {
        p2[i] = i * 5;
    }
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", p2[i]);
    }
    printf("\n");
    free(p2);
    p2 = NULL;

    // void* calloc(size_t n, size_t size);
    // calloc()会将所分配的内存全部初始化为0。malloc()不会对内存进行初始化，如果想要初始化为0，还要额外调用memset()函数。
    int *p3 = calloc(10, sizeof(int));
    if (p3 == NULL)
    {
        return 1;
    }
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", p3[i]); // 0 0 0 0 0 0 0 0 0 0
    }
    printf("\n");
    free(p3);

    // 等同于
    int *p4 = (int *)malloc(sizeof(int) * 10);
    if (p4 == NULL)
    {
        return 1;
    }
    memset(p4, 0, sizeof(int) * 10);
    free(p4);

    // void* realloc(void* block, size_t size)
    // realloc()可能返回一个全新的地址（数据也会自动复制过去），也可能返回跟原来一样的地址。realloc()优先在原有内存块上进行缩减，尽量不移动数据，所以通常是返回原先的地址。如果新内存块小于原来的大小，则丢弃超出的部分；如果大于原来的大小，则不对新增的部分进行初始化（程序员可以自动调用memset()）。

    int *b = (int *)malloc(sizeof(int) * 10);
    if (b == NULL)
    {
        return 1;
    }
    b = (int *)realloc(b, sizeof(int) * 2000);
    if (b == NULL)
    {
        return 1;
    }
    free(b);

    // realloc()的第一个参数可以是 NULL，这时就相当于新建一个指针。
    // char* p = realloc(NULL, 3490);
    // 等同于
    // char* p = malloc(3490);
    // 如果realloc()的第二个参数是0，就会释放掉内存块。
    // 注意，realloc()不会对内存块进行初始化。

    // restrict 说明符
    // 声明指针变量时，可以使用restrict说明符，告诉编译器，该块内存区域只有当前指针一种访问方式，其他指针不能读写该块内存。这种指针称为“受限指针”（restrict pointer）。

    int *restrict p5;
    p5 = (int *)malloc(sizeof(int));

    int *p6 = p5;
    *p6 = 1; // 未定义行为
    // 上面示例中，另一个指针p5与受限指针p6指向同一块内存，现在该内存有p5和p6两种访问方式。这就违反了对编译器的承诺，后面通过*p6对该内存区域赋值，会导致未定义行为。
    // 更详细关于restrict关键字：
    // 1、https://blog.csdn.net/llf021421/article/details/8092602
    // 2、https://zhuanlan.zhihu.com/p/349726808

    // void* memcpy(void* restrict dest, void* restrict source, size_t n);
    // dest和source都是 void 指针，表示这里不限制指针类型，各种类型的内存数据都可以拷贝。两者都有 restrict 关键字，表示这两个内存块不应该有互相重叠的区域。
    // memcpy()的返回值是第一个参数，即目标地址的指针。
    // 因为memcpy()只是将一段内存的值，复制到另一段内存，所以不需要知道内存里面的数据是什么类型。
    char s[] = "Goats!";
    char t[100];
    memcpy(t, s, sizeof(s)); // 拷贝7个字节，包括终止符
    printf("%s\n", t);       // "Goats!"

    // memmove()函数用于将一段内存数据复制到另一段内存。它跟memcpy()的主要区别是，它允许目标区域与源区域有重叠。如果发生重叠，源区域的内容会被更改；如果没有重叠，它与memcpy()行为相同。
    // void* memmove(void* dest, void* source, size_t n);
    char x[] = "Home Sweet Home";
    printf("%s\n", (char *)memmove(x, &x[5], 10)); // 输出 Sweet Home Home

    // memcmp()函数用来比较两个内存区域
    // int memcmp(
    // const void* s1,
    // const void* s2,
    // size_t n
    // );
    // 它接受三个参数，前两个参数是用来比较的指针，第三个参数指定比较的字节数。
    // 它的返回值是一个整数。两块内存区域的每个字节以字符形式解读，按照字典顺序进行比较，如果两者相同，返回0；如果s1大于s2，返回大于0的整数；如果s1小于s2，返回小于0的整数。
    char s1[] = {'b', 'i', 'g', '\0', 'c', 'a', 'r'};
    char s2[] = {'b', 'i', 'g', '\0', 'c', 'a', 't'};

    if (memcmp(s1, s2, 3) == 0) // true
    {
        printf("memcmp(s1, s2, 3) == 0\n");
    }
    if (memcmp(s1, s2, 4) == 0) // true
    {
        printf("memcmp(s1, s2, 4) == 0\n");
    }
    if (memcmp(s1, s2, 7) == 0) // false
    {
        printf("memcmp(s1, s2, 7) == 0\n");
    }
    return 0;
}